using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using ParserGenerator.Extensions;

namespace ParserGenerator.CharacterMapping
{
    [Serializable]
    public sealed class CharClass : IEquatable<CharClass>, IComparable<CharClass>, IComparable
    {
        private int _characters = -1;
        private string _text;

        public int? Number { get; }
        public CharInterval Universal { get; }
        public ReadOnlyCollection<CharInterval> Intervals { get; }

        public CharClass(int? number, CharInterval universal, ReadOnlyCollection<CharInterval> intervals)
        {
            if (number < 0)
                throw new ArgumentException(SR.CharClassNumberMustBeNonNegative, nameof(number));
            if (intervals == null)
                throw new ArgumentNullException(nameof(intervals));
            Number = number;
            Universal = universal;
            Intervals = intervals;
        }

        public int Characters
        {
            get
            {
                if (_characters == -1)
                {
                    int total = 0;
                    foreach (CharInterval item in Intervals)
                        total += item.Length;
                    _characters = total;
                }
                return _characters;
            }
        }

        public sealed override int GetHashCode()
        {
            return Intervals.GetListHashCode();
        }

        public bool Equals(CharClass other)
        {
            return other != null && Intervals.ListEquals(other.Intervals);
        }

        public sealed override bool Equals(object obj)
        {
            return obj is CharClass && Equals((CharClass)obj);
        }

        public int CompareTo(CharClass other)
        {
            if (other == this)
                return 0;
            if (other == null)
                return 1;
            return Intervals.ListCompareTo(other.Intervals);
        }

        public int CompareTo(object obj)
        {
            if (obj == null)
                return 1;
            else if (obj is CharClass)
                return CompareTo((CharClass)obj);
            else
                throw new ArgumentException(SR.ArgumentMustBeCharClass, nameof(obj));
        }

        public int Search(int c)
        {
            int i = 0;
            int j = Intervals.Count - 1;
            while (i <= j)
            {
                int k = i + (j - i) / 2;
                CharInterval item = Intervals[k];
                if (c < item.First)
                    j = k - 1;
                else if (c > item.Last)
                    i = k + 1;
                else
                    return k;
            }
            return ~i;
        }

        public bool Contains(int c)
        {
            int k = Search(c);
            return k >= 0;
        }

        public bool Includes(CharInterval item)
        {
            int k = Search(item.First);
            if (k >= 0)
                return item.Last <= Intervals[k].Last;
            else
                return false;
        }

        public bool Includes(CharClass other)
        {
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            foreach (CharInterval item in other.Intervals)
                if (!Includes(item))
                    return false;
            return true;
        }

        public static CharClass Negate(CharClass value)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            List<CharInterval> items = new List<CharInterval>(value.Intervals);
            CharIntervalHelpers.Negate(items, value.Universal);
            return new CharClass(null, value.Universal, items.ToArray().AsReadOnly());
        }

        public static CharClass Union(CharClass value, CharClass other)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (!value.Universal.Equals(other.Universal))
                throw new ArgumentException(SR.UniversalMismatch);
            List<CharInterval> items = new List<CharInterval>(value.Intervals);
            CharIntervalHelpers.Merge(items, other.Intervals);
            return new CharClass(null, value.Universal, items.ToArray().AsReadOnly());
        }

        public static CharClass Intersect(CharClass value, CharClass other)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (!value.Universal.Equals(other.Universal))
                throw new ArgumentException(SR.UniversalMismatch);
            List<CharInterval> items = new List<CharInterval>(value.Intervals);
            List<CharInterval> items2 = new List<CharInterval>(other.Intervals);
            CharIntervalHelpers.Negate(items, value.Universal);
            CharIntervalHelpers.Negate(items2, value.Universal);
            CharIntervalHelpers.Merge(items, items2);
            CharIntervalHelpers.Negate(items, value.Universal);
            return new CharClass(null, value.Universal, items.ToArray().AsReadOnly());
        }

        public static CharClass Except(CharClass value, CharClass other)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (!value.Universal.Equals(other.Universal))
                throw new ArgumentException(SR.UniversalMismatch);
            List<CharInterval> items = CharIntervalHelpers.Difference(value, other);
            return new CharClass(null, value.Universal, items.ToArray().AsReadOnly());
        }

        public static CharClass SymmetricExcept(CharClass value, CharClass other)
        {
            if (value == null)
                throw new ArgumentNullException(nameof(value));
            if (other == null)
                throw new ArgumentNullException(nameof(other));
            if (!value.Universal.Equals(other.Universal))
                throw new ArgumentException(SR.UniversalMismatch);
            List<CharInterval> items = CharIntervalHelpers.Difference(value, other);
            List<CharInterval> items2 = CharIntervalHelpers.Difference(other, value);
            CharIntervalHelpers.Merge(items, items2);
            return new CharClass(null, value.Universal, items.ToArray().AsReadOnly());
        }

        public sealed override string ToString()
        {
            if (_text == null)
            {
                if (Intervals.Count == 1 && Intervals[0].Length == 1)
                    _text = CharIntervalHelpers.FormatChar(Intervals[0].First);
                else
                    _text = CharIntervalHelpers.FormatClass(Intervals, Universal);
            }
            return _text;
        }
    }
}
